package com.xiaok.netty.client.handle;

import com.alibaba.fastjson.JSON;
import com.xiaok.common.entity.MonitorVo;
import com.xiaok.common.protobuf.Command;
import com.xiaok.common.protobuf.Message;
import com.xiaok.common.util.BeanUtils;
import com.xiaok.netty.client.initializer.TCPClient;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * @Classname HeartHandler
 * @Description TODO
 * @Date 2020/10/8 17:55
 * @Created by xiaok
 */
@Component("heartHandler")
public class HeartHandler extends ChannelInboundHandlerAdapter {
    public Logger log = LoggerFactory.getLogger(this.getClass());

    @Autowired
    @Qualifier("tcpClient")
    private TCPClient tcpClient;


    // 定义客户端没有收到服务端的pong消息的最大次数
    private static final int MAX_UN_REC_PONG_TIMES = 3;
    // 多长时间未请求后，发送心跳
    private static final int WRITE_WAIT_SECONDS = 5;//暂时未使用

    // 隔N秒后重连
    private static final int RE_CONN_WAIT_SECONDS = 5;//暂时未使用

    // 客户端连续N次没有收到服务端的pong消息  计数器
    private int unRecPongTimes = 0 ;
    @Value("${spring.netty.clientId}")
    private  String clientId;  //客户端的唯一标识符
    @Autowired
    RedisTemplate redisTemplate;

    private int heartbeatCount = 0;
    private long ccTime = 0;//缓存发送时间 单位毫秒

    private final DateFormat df = new SimpleDateFormat("HH:mm:ss");


    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        Message.MessageBase message = ( Message.MessageBase) msg;
        Command.CommandType cmdType = message.getCmd();
        log.info("cmdType: "+cmdType);
        if(cmdType.equals(Command.CommandType.PUSH_DATA_BACK)){
            log.info("接收到的数据: "+message.getData());
            }else if(cmdType.equals(Command.CommandType.PING)){

             }else if(cmdType.equals(Command.CommandType.PONG)){
            //接收到server发送的pong指令
            unRecPongTimes = 0;
            //计算ping值
            long ping = (System.currentTimeMillis()-ccTime)/2;
            log.info("客户端和服务器的ping是"+ping+"ms");
            //log.info(msg.getData());
            MonitorVo monitorVo = new MonitorVo();
            monitorVo.setClientId(clientId);
            monitorVo.setDateList(df.format(new Date()));
            monitorVo.setValueList(ping);
            redisTemplate.opsForList().leftPush("heart:monitor:"+clientId, JSON.toJSONString(monitorVo));
            sendPingMsg(ctx,clientId);
            //计算此时的时间
            //	log.info(msg.getData());
            }
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            String type = "";
            if (event.state() == IdleState.READER_IDLE) {
                type = "read idle";
            } else if (event.state() == IdleState.WRITER_IDLE) {
                type = "write idle";
            } else if (event.state() == IdleState.ALL_IDLE) {
                type = "all idle";
            }
            if(unRecPongTimes < MAX_UN_REC_PONG_TIMES){
                sendPingMsg(ctx,clientId);
                unRecPongTimes++;
            }else{ //超过关闭
                ctx.channel().close();
            }
            log.info("心跳类型type: "+type);
            log.info("超时次数: "+unRecPongTimes);
        } else {
            super.userEventTriggered(ctx, evt);
        }
    }

    private void sendPingMsg(ChannelHandlerContext ctx, String clientid) {
        heartbeatCount++;
        ccTime = System.currentTimeMillis();
        ctx.writeAndFlush( createData(clientid,Command.CommandType.PING,String.valueOf(ccTime)).build());
        log.info("Client sent ping msg to " + ctx.channel().remoteAddress() + ", count: " + heartbeatCount);
    }

    /**
     * channel 处理处于不活跃的状态
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        log.warn("检测到与心跳服务器断开");
        ctx.channel().eventLoop().schedule(()->{tcpClient.run();},RE_CONN_WAIT_SECONDS, TimeUnit.SECONDS);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.warn("发生异常: "+cause.getMessage());
        ctx.close();
    }
    /**
     * 创建 protobuf 的不同类型的数据
     * @param clientId
     * @param cmd
     * @param data
     * @return
     */
    private Message.MessageBase.Builder createData(String clientId, Command.CommandType cmd, String data){
        Message.MessageBase.Builder msg = Message.MessageBase.newBuilder();
        //封装数据
        msg.setClientId(clientId);
        msg.setCmd(cmd);
        msg.setData(data);
        return msg;

    }
}
